; 主引导程序
core_base_address       equ     0x00040000   ;常数，内核加载的起始内存地址 
core_start_sector       equ     0x0001   ;常数，内核的起始逻辑扇区号 
FLPY_SECTORS_PER_TRACK  equ     18
FDC_DOR                 equ     0x3F2  ; 数字输出寄存器
FDC_MSR                 equ     0x3F4  ; 主状态寄存器
FDC_FIFO                equ     0x3F5  ; 数据寄存器

;==================================
;头部协议区
;==================================
SECTION header vstart=0
    program_length dd program_end
    ; 程序入口
    code_entry dw start
               dd section.code.start
    realloc_tbl_len dw (header_end-segment_realloc_table)/4
    ; 程序段重定位表
    segment_realloc_table:
    code_segment dd section.code.start
    data_segment dd section.data.start
    header_end:
;===================================
SECTION code align=16 vstart=0
start:
; 初始化数据段
mov ax, [data_segment]
mov ds, ax
mov es, ax

; 思路，先读取内核程序头部，了解内核程序总长度及其分段
; 加载内核分段到 gdt
; 跳转到内核程序入口点

;计算GDT所在逻辑段的地址
mov eax,[es:pgdt+0x02]
xor edx,edx
mov ebx,16
div ebx

mov ds,eax  ; eax是段地址
mov ebx,edx ; edx是偏移地址

mov dword [ebx],0x00000000  
mov dword [ebx+0x04],0x00000000 

mov dword [ebx+0x08],0x0000_ffff   ;装配描述符
mov dword [ebx+0x0c],0x00cf_9200

mov dword [ebx+0x10],0x0020_03ff   ;代码段
mov dword [ebx+0x14],0x0040_9801

mov dword [ebx+0x18],0x7c00fffe    ;栈段
mov dword [ebx+0x1c],0x00cf9600

;建立保护模式下的显示缓冲区描述符   
mov dword [ebx+0x20],0x80007fff    ;基地址为0x000B8000，界限0x07FFF 
mov dword [ebx+0x24],0x0040920b    ;粒度为字节

; 初始化GDTR
mov word [es:pgdt],39   ;GDT的界限=total bytes-1
lgdt [es:pgdt]  ; load GDT

in al,0x92
or al,0000_0010B
out 0x92,al     ;通过南桥芯片ICH打开A20地址线

cli   ;保护模式下中断机制与实模式不同，没有建立新的中断机制之前，可以先禁止中断

mov eax,cr0
or eax,1   ; 将cr0的0位设为1，标志着进入保护模式
mov cr0,eax

; 从这里开始进入保护模式
jmp dword 0x0010:flush   ; 这里的0008是段选择子

[bits 32]
flush:
;设置数据段和扩展段
mov eax,0x08
mov ds,eax
mov eax,0x20
mov es,eax
;设置栈段
mov eax,0x18
mov ss,eax
xor esp,esp
;先读取程序头部
mov eax,[0x10000+data_segment]
mov ecx,0x10
mul ecx
mov ebp,eax

mov eax,core_start_sector
mov ebx,core_base_address
call read_floppy_disk_0
call @wait

;获取程序总长度，并继续读取，直到所有扇区全部load进内存
mov edi, core_base_address
mov eax,[edi]  ; 总长度对其到 512 字节

mov ebx,eax
and ebx,0xFFFFFE00
add ebx,512
test eax,0x000001ff
cmovnz eax,ebx

xor edx,edx
mov ecx,512
div ecx
dec eax   ; 已经读取了一个扇区，所以减 1
mov ecx,eax ; 读取次数
mov eax,core_start_sector 
inc eax  ; 本次读取的扇区号
mov ebx,core_base_address ; 载入的线性基地址
add ebx,512
read_continue:
    call read_floppy_disk_0
    call @wait
    inc eax
    loop read_continue

setup:
mov esi,[0x10000+section.data.start+0x02] ; 通过4GB的段来修改属于代码段的内存
;建立公用例程段描述符
mov eax,[edi+0x04]                 ;公用例程代码段起始汇编地址
mov ebx,[edi+0x08]                 ;核心数据段的起始汇编地址，用于计算段界限
sub ebx,eax
dec ebx
add eax,edi     ; 公用例程代码段的线性基地址
mov ecx,0x00409800  ; 字节粒度的代码段属性描述符
call make_gdt_descriptor
mov [esi+0x28],eax
mov [esi+0x2c],edx

;建立核心数据段描述符
mov eax,[edi+0x08]                 ;核心数据段起始汇编地址
mov ebx,[edi+0x0c]                 ;核心代码段的起始汇编地址，用于计算核心数据段界限
sub ebx,eax
dec ebx
add eax,edi     ; 核心数据段的线性基地址
mov ecx,0x00409200  ; 字节粒度的核心数据段属性描述符
call make_gdt_descriptor
mov [esi+0x30],eax
mov [esi+0x34],edx

;建立核心代码段描述符
mov eax,[edi+0x0c]                 ;核心代码段起始汇编地址
mov ebx,[edi+0x00]                 ;程序总长度
sub ebx,eax
dec ebx
add eax,edi     ; 核心代码段的线性基地址
mov ecx,0x00409800  ; 字节粒度的核心代码段属性描述符
call make_gdt_descriptor
mov [esi+0x38],eax
mov [esi+0x3c],edx

mov word [0x10000+section.data.start],63          ;描述符表的界限
lgdt [0x10000+section.data.start]

jmp far [edi+0x10]

@wait:
push ecx
mov ecx, 1000000 ; 这个值根据 CPU 频率调整，模拟 1 秒
.delay:
    dec ecx
    jnz .delay          ; 循环直到 ECX 为 0
pop ecx
ret

;================
; 读 1 个扇区的软盘数据到内存
; eax -- 起始扇区
; ebx -- 载入内存的线性基地址
;================
read_floppy_disk_0:
    push eax
    push ecx
    push edx
    push ds
    push es
    push ebx

    mov ebx,eax

    ; 再计算柱面号 
    mov ecx,FLPY_SECTORS_PER_TRACK*2
    xor edx,edx
    div ecx
    mov [ds:ebp+@3],al

    ; 再计算磁头号
    mov ecx,FLPY_SECTORS_PER_TRACK*2
    xor edx,edx
    mov eax,ebx
    div ecx
    mov eax,edx
    xor edx,edx
    mov ecx,FLPY_SECTORS_PER_TRACK
    div ecx
    mov [ds:ebp+@3+1],al

    ; 计算扇区号
    mov ecx,18
    mov eax,ebx
    xor edx,edx
    div ecx
    inc edx
    mov [ds:ebp+@3+2],dl

    pop ebx

    ; 设置 DMA
    call setup_dma
    ; 重置软盘控制器
    call reset_floppy
    ; 读取数据
    call read_sector

    add ebx,512

    pop es
    pop ds
    pop edx
    pop ecx
    pop eax
    ret

setup_dma:
    ; 1. 禁用 DMA 通道 2
    mov dx, 0x0A       ; DMA 主屏蔽寄存器端口
    mov al, 0x04       ; 禁用通道 2
    out dx, al

    ; 2. 设置 DMA 地址寄存器
    mov eax, ebx
    mov dx, 0x04       ; 通道 2 的地址寄存器
    out dx, al         ; 输出低 8 位
    mov al, ah         ; 高 8 位
    out dx, al

    ; 3. 设置 DMA 页寄存器
    mov dx, 0x81       ; 通道 2 的页寄存器
    shr eax, 16        ; 数据缓冲区段地址（高 8 位段地址）      
    out dx, al

    ; 4. 设置 DMA 传输长度（计数寄存器）
    mov dx, 0x05       ; 通道 2 的计数寄存器
    mov ax, 511        ; 传输字节数减 1（512 - 1 = 511）
    out dx, al         ; 输出低 8 位
    mov al, ah         ; 高 8 位
    out dx, al

    ; 5. 设置 DMA 模式寄存器
    mov dx, 0x0B       ; DMA 模式寄存器
    mov al, 0x56       ; 通道 2，读模式，单字节传输
    out dx, al

    ; 6. 启用 DMA 通道 2
    mov dx, 0x0A       ; DMA 主屏蔽寄存器
    mov al, 0x02       ; 启用通道 2
    out dx, al

; 该设置floppy了
reset_floppy:
    ; 发送复位命令
    mov dx, FDC_DOR
    mov al, 0x00       ; 复位软盘控制器
    out dx, al
    mov al, 0x1C       ; 重新启用软盘控制器和驱动器 0
    out dx, al

        ; 等待复位完成
    call wait_floppy_ready
    ret

    ; 等待软盘控制器就绪
    wait_floppy_ready:
        mov dx, FDC_MSR
    .wait:
        in al, dx
        test al, 0x80      ; 检查主状态寄存器的忙位（第 7 位）
        jz .wait          ; 如果忙，继续等待
        ret

; ============================
; 3. 读取软盘扇区
; ============================
read_sector:
    ; 发送 READ DATA 命令到软盘控制器
    mov dx, FDC_FIFO
    mov al, 0xE6       ; 命令：读数据
    out dx, al
    mov al, [ds:ebp+@3+1]       ; 起始头号
    shl al, 2
    or  al, 0x00
    out dx, al
    mov al, [ds:ebp+@3]       ; 起始磁道号
    out dx, al
    mov al, [ds:ebp+@3+1]       ; 起始头号
    out dx, al
    mov al, [ds:ebp+@3+2]       ; 起始扇区号（第2个扇区）
    out dx, al
    mov al, 0x02       ; 每扇区 512 字节
    out dx, al
    mov al, [ds:ebp+@3+2]
    inc al
    out dx, al
    mov al, 0x1B       ; GAP3 长度
    out dx, al
    mov al, 0xFF       ; 数据长度（无关）
    out dx, al

    ; 等待操作完成
    call wait_floppy_ready

    ret 
;====================
; 生成 GDT 描述符
; EAX —— 线性基地址
; EBX —— 段界限
; ECX —— 属性（均在原始位置）
; 返回 EDX：EAX 作为描述符
;====================
make_gdt_descriptor:
mov edx,eax ; 因为基地址在描述符的高32位和低32位都有一半数据，因此复制一份，并以此为基础生成描述符
shl eax,16 ; 左移16位
or ax,bx  ; 低16位置为段界限15~0

and edx,0xffff0000 ; 只保留高16位
rol edx,8   ; 循环左移8位
bswap edx   ; 字节互换，最高8位和最低8位互换，中间高8位和低8位互换

xor bx,bx   ; 清除ebx的低16位
or edx,ebx  ; 将ebx的高4位插入edx
or edx,ecx  ; 将属性值插入edx
ret

;===================================
SECTION data align=16 vstart=0
;-------------------------------------------------------------------------------                             
     pgdt             dw 0
                      dd 0x00007e00      ;GDT的物理地址
;-------------------------------------------------------------------------------                       
@3: db 0,0,0    ; 用于存储 柱面、磁头、扇区   
;===================================
SECTION trailer align=16
program_end: